﻿//#define DEBUG

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using GameLogic.Objects;
namespace GameLogic
{
    public class Game
    {
        internal List<GameObject> GameObjects
        {
            get
            {
                return GameMap.Objects;
            }
            set
            {
                GameMap.Objects = value;
            }
        }

        internal List<GameObject> AliveObjects
        {
            get
            {
                return GameMap.AliveObjects;
            }
        }

        public int TanksNowAlive { get; private set; }

        public Keys[] KeysToListen { get; set; }
        public bool GameOver { get; private set; }

        public int Width
        {
            get
            {
                return GameMap.Width;
            }
        }
        public int Height
        {
            get
            {
                return GameMap.Height;
            }
        }

        private Map GameMap { get; set; }
        public int KilledCount { get; set; }
        private PlayerTank Player { get; set; }
        public int PlayerHitPoints
        {
            get
            {
                return Player == null ? 0 : Player.HitPoints;
            }
        }

        private TankFactory Factory { get; set; }
        private Random Random { get; set; }

        public Game(int width, int height)
        {
            Random = new Random();
            GameMap = Map.Generate(width, height, Random);
            Player = CreatePlayer();
            GameObjects.Add(Player);
            AliveObjects.Add(Player);
            Player.OnShot += OnShot;
            KilledCount = 0;
            TanksNowAlive = 0;
            Factory = new TankFactory(Random);
            KeysToListen = new Keys[]
            {
                Keys.Space,
                Keys.A,
                Keys.W,
                Keys.S,
                Keys.D,
            };
            GameOver = false;
        }

        private PlayerTank CreatePlayer()
        {
            return new PlayerTank();
        }

        public void Tick(Graphics g)
        {
            if (!GameOver)
            {
                TryAddTanks();
                HandleKeys();
                foreach (var gameObject in GameObjects)
                {
                    if (gameObject is MovingObject && !(gameObject is PlayerTank))//один раз уже сходил, хватит
                        (gameObject as MovingObject).Move(GameMap);
                    if (gameObject is Bullet)
                    {
                        var bullet = gameObject as Bullet;
                        if (!IsInField(bullet))
                        {
                            AliveObjects.Remove(gameObject);
                        }
                    }
                }

                foreach (var gameObject in GameObjects)
                {
                    gameObject.Draw(g);
                }

                GameObjects = Utils.RemoveDead(GameObjects, AliveObjects).ToList();
            }
        }

        private void TryAddTanks()
        {
            Tank[] tanks = Factory.GetNext(TanksNowAlive);
            if (tanks == null) return;
            AddTanks(tanks);
        }

        private void AddTanks(Tank[] tanks)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            foreach (var tank in tanks)
            {
                //int x = Width - Constants.TANK_WIDTH / 2, y = Constants.TANK_WIDTH / 2;//правый верхний угол
                int x = Width - Constants.TANK_WIDTH, y = 0;
                while (true)
                {
                    Rectangle possiblePosition = new Rectangle(x, y, Constants.TANK_WIDTH, Constants.TANK_HEIGHT);
                    if (!tank.IntersectsWithSmth(possiblePosition, GameMap))// && IsInField(tank)
                    {
                        tank.Position = possiblePosition;
                        tank.CurrentDirection = Direction.WEST;
                        AddObject(tank);
                        TanksNowAlive++;
                        break;
                    }

                    long elapsed = watch.Elapsed.Milliseconds;
                    if (elapsed > Constants.TICK_TIME / 2)
                    {
                        watch.Stop();
                        return;//видимо, на поле достаточно объектов
                    }

                    //не получилось добавить, пробуем добавить клеткой ниже. Или поближе к левому краю
                    y += Constants.TANK_HEIGHT;
                    if (y > Height - Constants.TANK_HEIGHT)
                    {
                        x -= Constants.TANK_WIDTH;
                        y = 0;
                    }
                }
            }
        }

        [DllImport("User32.dll")]
        public static extern int GetKeyState(int key);

        private void HandleKeys()
        {
            foreach (var key in KeysToListen)
            {
                var state = GetKeyState((int)key);
                if (
                    state == 127 ||
                    state == 128 ||
                    state == -127 ||
                    state == -128
                    )
                {
                    if (key == Keys.Space)
                    {
                        Player.Shot();
                    }
                    else
                    {
                        Direction preferredDirection = GetDirection(key);
                        Player.CurrentDirection = preferredDirection;
                        Player.Move(GameMap);
                        return;
                    }
                }
            }
        }

        private Direction GetDirection(Keys key)
        {
            switch (key)
            {
                case Keys.A:
                    return Direction.WEST;
                case Keys.W:
                    return Direction.NORTH;
                case Keys.S:
                    return Direction.SOUTH;
                case Keys.D:
                default:
                    return Direction.EAST;
            }
        }

        internal void OnShot(object sender, BulletCreationEventArgs e)
        {
            var tank = sender as Tank;
            var bullet = e.Bullet;
            if (bullet != null && IsInField(bullet))
            {
                bullet.IsPlayerBullet = e.IsPlayerBullet;
                AddObject(bullet);
            }
        }

        private void BulletBlow(object sender, BlowEventArgs e)
        {
            var bullet = sender as Bullet;
            if (!AliveObjects.Remove(bullet))
            {
                throw new Exception("не удалили пулю");
            }
            if (e.Target != null)
            {
                if (EnemyShotsEnemy(bullet.Creator, e.Target))// враги стреляют друг в друга, проще игнорировать это, иначе они быстро дойдут до 20 на поле
                {
                    return;
                }
                e.Target.HitPoints--;
                if (e.Target.HitPoints == 0)
                {
                    if (!AliveObjects.Remove(e.Target))
                    {
                        throw new Exception("не удалили объект");
                    }
                    if (e.Target == Player)
                    {
                        Player = null;
                        Stop();
                    }
                    else if (e.Target is Tank && (sender as Bullet).IsPlayerBullet)
                    {
                        TanksNowAlive--;
                        KilledCount++;
                    }
                }
            }
        }

        private bool EnemyShotsEnemy(Tank shooter, GameObject target)
        {
            if (shooter is PlayerTank || target is PlayerTank)
            {
                return false;
            }
            // => shooter is enemy
            if (target is Tank)// но не Player
            {
                return true;
            }
            return false;
        }

        private void Stop()
        {
            GameOver = true;
        }

        private bool IsInField(GameObject obj)
        {
            Rectangle field = new Rectangle(0, 0, Width, Height);
            var position = obj.GetPhysRectangle();
            return field.Contains(position);
        }

        private void AddObject(GameObject obj)
        {
            if (obj is PlayerTank)
            {
                (obj as PlayerTank).OnShot += OnShot;
            }
            else if (obj is Tank)
            {
                (obj as Tank).OnShot += OnShot;
            }
            if (obj is Bullet)
            {
                var bullet = obj as Bullet;
                bullet.Blow += BulletBlow;
                GameObject collidedObject = bullet.GetCollisedObject(bullet.GetPhysRectangle(), GameMap);
                AliveObjects.Add(bullet);
                if (collidedObject != null && collidedObject != bullet.Creator)
                {
                    bullet.OnHit(collidedObject);
                }
            }
            else
            {
                AliveObjects.Add(obj);
            }
        }
    }
}
